iT邦幫忙

2024 iThome 鐵人賽

0
自我挑戰組

前端工程師的java學習紀錄系列 第 40

Day40-集合Collection(二)-List、Set

  • 分享至 

  • xImage
  •  

List

List中有三個實作類

  • ArrayList(使用List時主要使用的實作類):基本上是array ,但是可以把它當成動態 的array,當新增長度不夠時,底層 會自動擴充長度,因為有index適用於查詢 元素。
  • LinkedList:每一個元素都會有對應的上一個元素和下一個元素的資訊,適用於插入刪除 元素。
  • Vector:java最一開始的List 類,後來在jdk5.0後新增了ListArrayList 後,把它納入List 底下,基本上已經很少使用此類。

常用的方法

  • void add(int index, Object ele):在index處新增一個元素。(註:ele代表element 元素,只是一個約定的命名方式,較為簡潔,不是必要)
  • boolean addAll(int index, Collection eles):在索引處新增一個集合。
  • Object get(int index):獲得index處的一個元素。
  • List subList(int fromIndex, int toIndex):返回一個從fromIndextoIndex 的子集合。
  • int indexOf(Object obj) :返回首次出現物件的索引。
  • int lastIndexOf(Object obj) :返回最後 出現物件的索引。
  • Object remove(int index) :刪除指定index處的物件,並將它返回。
  • Object set(int index, Object ele) :將指定index處物件換成ele ,並返回被取代 的物件。

比較要小心的是remove(int index) 方法

ArrayList list = new ArrayList();
list.add("abc");
list.add("bbb");
list.add(123);
list.remove("abc");
//list.remove(123); 當使用這樣想刪除123時,會出現錯誤

Collection 只能存放引用類別 的資料,當新增基本類別 的資料時,會用包裝類將它們包裝成引用類別 ,所以如果要刪除123 時,直接在參數中放入123 會認為是要刪除位於index 123的資料。

必須這樣使用才能夠正確地將123 刪除

list.remove(Integer.valueOf(123));

Set

Set中有三個實作類

  • HashSet(使用Set時主要使用的實作類):底層是使用HashMap 的資料結構(之後會說)。
  • LinkedHashSet:是HashSetsubclass ,多加了雙向鏈表 紀錄元素新增的先後順序,所以可以按照元素新增的順序進行遍歷,也可以用於頻繁的查詢。
  • TreeSet:底層使用紅黑樹的資料結構,新增資料時會將元素進行排序,也由於會進行資料的排序,所以要求新增的資料型別必須相同 ,否則會出現ClassCastException 異常。

Set中沒有另外新增額外的方法,都是使用Collection 的方法。


HashSet & LinkedHashSet

這兩個Set 的實作類是使用hashCode()equals() 方法去判斷是否存在重複,因此在新增自定義的時,需要將hashCode()equals() 進行重寫。

class Person {
	String name;
	int age;
}
HashSet set = new HashSet();
set.add(new Person("Tom",30));
set.add(new Person("Tom",30));
Ierator iterator = set.iterator();
while(iterator.hasNext()) {
	System.out.println(iterator.next());
}
Person{name='Tom', age=30}
Person{name='Tom', age=30}

由於沒有重寫hashCode()equals() 方法,所以這兩個新增的Person類 明明是相同的內容(記憶體中的地址不同),卻還是加到Set 中了

Person類修改(重寫hashCode()equals() 方法)

class Person {
	//...略
	@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

這時候如果再重複新增一次時,就只會顯示一個Person{name='Tom', age=30} 了。

它的原理是這樣的:

  1. 將這個類的內容先進行hash算法 後放入一個陣列 中的某個位置(不會按照陣列的索引擺放,所以是無序 )
  2. hash 後要放入的陣列 索引處已經有東西時,會使用equals() 進行比較,如果不相等時,才會將這個類放入Set

TreeSet

Set無序不可重複 的,但是TreeSet 在判斷是否重複時跟HashSetLinkedHashSet 判斷的方式不同,它是已compareTo()compare() 去判斷元素是否重複,所以如果是放入自定義的 時,需要將該類實現Comparable 接口或是另外實作Comparator 類進行判斷。

TreeSet:加入不同類型的元素時,運行會報異常(編譯不會)

TreeSet set = new TreeSet();
set.add("aa");
set.add("bb");
set.add("cc");
//set.add(123); ClassCastException

假設目前有一個User 類(實作Comparable 接口)

class User implements Comparable {
	String name;
	int age;
	
	// 為了避免內容過於複雜,省略了構造器、toString等方法,只專注在這個部分所需要使用到的東西
	
	@override
	public int compareTo(Object obj) {
		if(this == obj) {
			return 0;
		}
		
		if(obj instanceOf User) {
			User o = (User) obj;
			return this.age - o.age;
		}
		
		throw new RuntimeException("Wrong Type");
}
TreeSet set = new TreeSet();

User user1 = new User("Tom", 20);
User user2 = new User("Amy", 30);
User user3 = new User("John", 20);

Iterator iterator = set.iterator();
while(iterator.hasNext()) {
	System.out.println(iterator.next());
}

User{name='Tom', age=20}
User{name='Amy', age=30}

由於重寫的compareTo() 方法沒有比較name 屬性,所以就算user1user3name 屬性不同,TreeSet 還是將它們判斷為重複的資料。

User中的compareTo()修改

@override
	public int compareTo(Object obj) {
		if(this == obj) {
			return 0;
		}
		
		if(obj instanceOf User) {
			User o = (User) obj;
			int compareValue = this.age - o.age;
			if(compareValue != 0) {
				return compareValue;
			}
			
			return this.name.compareTo(o.name); // 若age相同時,進行name的比較
		}
		
		throw new RuntimeException("Wrong Type");

age 相同時,會進行name 屬性的比較,因為String類 已經有copareTo() 了,所以這邊可以直接透過this.name.compareTo(o.name); 直接使用。


使用Comparator接口(User基本上只差別在於有沒有Comparable ,因此就直接拿來使用了)

Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User) {
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    int compareName = u1.name.compareTo(u2.name);
                    if(compareName != 0) {
                        return compareName;
                    }
                    return u1.age - u2.age;
                }
                throw new RuntimeException("Wrong Type");
            }
        };

User user1 = new User("Tom", 20);
User user2 = new User("Amy", 30);
User user3 = new User("John", 20);
TreeSet set = new TreeSet(comparator); //需要將實作的comparator放入實例化的參數中
        set.add(user1);
        set.add(user2);
        set.add(user3);

Iterator iterator = set.iterator();
while(iterator.hasNext()) {
	System.out.println(iterator.next());
}
User{name='Amy', age=30}
User{name='John', age=20}
User{name='Tom', age=20}

由於是先進行name 的排序,接著才排序age ,所以這邊排序後,因為沒有相同的問題發生,就會按照name 的順序印出資料。


增強型for 循環(for-each循環)

用於ArrayListSet 的循環語法糖,會對每一個元素進行迭代

好處:避免越界錯誤(IndexOutOfBoundsException)、簡潔、不用特別調用Iterator 迭代器。

缺點:無法對索引 直接進行操作

語法

for (Type variable : IterableOrArray) {
    // 在每次迭代中使用 variable
}

用法範例

ArrayList list = new ArrayList();
list.add("abc");
list.add("bbb");
list.add(123);

for(Object obj : list) {
	System.out.println(obj);
}
abc
bbb
123

需要特別注意的是,因為增強for循環 的底層也是使用Iterator ,所以如果是自定義的類需要使用時,必須先實作Iterator 接口才能這樣使用。


上一篇
Day39-集合Collection
下一篇
Day41-Map
系列文
前端工程師的java學習紀錄41
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言